﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using AppProperties = WPF.TestEditor.Properties;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.CodeCompletion;
using WPF.TestEditor.Common;
using ICSharpCode.AvalonEdit.Editing;
using EasyStep.Business.Interfaces;
using EasyStep.Attributes;
using ICSharpCode.AvalonEdit;
using EasyStep.Data.Entities;
using ICSharpCode.AvalonEdit.Rendering;
using ICSharpCode.AvalonEdit.Document;
using System.Text.RegularExpressions;
using System.Diagnostics;

namespace WPF.TestEditor.Controls
{

    public partial class TestTextEditor : UserControlBase
    {
        public event EventHandler DataChanged;
        public event EventHandler<MessageArgs> StatusChanges;
        private System.Timers.Timer timer = new System.Timers.Timer(50);
        public TestTextEditor()
            : base()
        {
            InitializeComponent();
            txtEditor.SyntaxHighlighting = null;
            txtEditor.TextArea.TextEntering += TextArea_TextEntering;
            txtEditor.TextArea.TextEntered += TextArea_TextEntered;
            txtEditor.HighlightLine += new EventHandler<ColorizingLineEventArgs>(txtEditor_HighlightLine);
            txtEditor.Options = new TextEditorOptions(txtEditor.Options)
            {
                CutCopyWholeLine = true,
                EnableEmailHyperlinks = false,
                EnableHyperlinks = false
            };
            txtEditor.TextChanged += new EventHandler(txtEditor_TextChanged);
            completionHelper = new DynamicACCollection(this.TestDataContext);
            timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed);
            
        }


        private Action onTimer;
        void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            timer.Stop();
            if (onTimer != null)
            {
                var method = onTimer;
                onTimer = null;                
                Dispatcher.Invoke(method);
            }
        }

        /// <summary>
        /// needs to highlight each line of code
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void txtEditor_HighlightLine(object sender, ColorizingLineEventArgs e)
        {
            ShowInfo("start line validation: {0}",e.line.LineNumber);
            if (string.IsNullOrEmpty(e.LineText)) return;
            var text = e.LineText.ToLowerInvariant();
            var index = text.IndexOf("=");
            var leftparh = index > -1 ? text.Substring(0, index) : text;
            var rightpart = index > -1 ? text.Substring(index + 1) : "";
            QuickCheckAction(e, leftparh, () =>
            {//if quick can't validate left part => we check it separately but longer 
                List<TestAction> result = null;
                if (!string.IsNullOrEmpty(leftparh))
                {
                    index = leftparh.IndexOf(",");
                    if (index < 0)
                        result = ValidateAction(e.lineStartOffset, () => actions, leftparh, e);
                    else
                    {
                        result = ValidateAction(e.lineStartOffset + index + 1, () =>
                        {
                            var l = ValidateAction(e.lineStartOffset, () => actions, leftparh.Substring(0, index), e);
                            var r = normalizedActions.Where(sa => l.Where(li => li.Id.Equals(sa.ParentId)).Any())
                                .ToList();
                            return r;
                        }
                        , leftparh.Substring(index + 1)
                        , e);
                    }
                }
                return result;
            });
            ShowInfo("Validate line right part: {0}", e.line.LineNumber);
            if (!string.IsNullOrEmpty(rightpart))
            {
                var offset = e.lineStartOffset + leftparh.Length + 1;
                var constsColorKey = txtEditor.SyntaxHighlighting.GetNamedColor("Constants");
                var testsColorKey = txtEditor.SyntaxHighlighting.GetNamedColor("Tests");
                completionHelper.CheckRegex(constsTestsRegEx, rightpart, (m) =>{
                    var g = m.Groups[1].Success ? m.Groups[1] : m.Groups[2].Success ? m.Groups[2] : m.Groups[0];
                    e.OnHighlightLineSegment(offset + g.Index, offset + g.Index + g.Length, m.Groups[1].Success?constsColorKey:testsColorKey);
                });
            }            
            ShowInfo("finished line validation: {0}", e.line.LineNumber);
        }
        public bool QuickCheckAction(ColorizingLineEventArgs e, string text, Func<List<TestAction>> onCantCheckQuick)
        {
            var result = completionHelper.CheckRegex(actionsRegEx, text, (m) =>
            {
                e.OnHighlightLineSegment(e.lineStartOffset + m.Index, e.lineStartOffset + m.Index + m.Length, txtEditor.SyntaxHighlighting.GetNamedColor("CorrectAction"));
            });            
            if (!result)
                onCantCheckQuick();
            
            return result;
        }

        private List<TestAction> ValidateAction(int offset, Func<List<TestAction>> onGetAvailableActions, string textToValidate, ColorizingLineEventArgs e)
        {
            var pattern = "^\\s*" + EscapeSpecChars(textToValidate.Trim()) + "\\s*$";
            var result = onGetAvailableActions().Where(a => System.Text.RegularExpressions.Regex.IsMatch(a.Name, pattern, System.Text.RegularExpressions.RegexOptions.IgnoreCase)).ToList();
            var colorKey = (result.Count > 0)
                ? txtEditor.SyntaxHighlighting.GetNamedColor("CorrectAction")
                : txtEditor.SyntaxHighlighting.GetNamedColor("IncorrectAction");
            e.OnHighlightLineSegment(offset, textToValidate.Length + offset, colorKey);
            return result;
        }

        DynamicACCollection completionHelper;
        WPF.TestEditor.Controls.AvalonTextEditor.CompletionWindow completionWindow;

        void TextArea_TextEntered(object sender, TextCompositionEventArgs e)
        {
            var area = (sender as TextArea);
            if (e.Text.Length > 0 && completionWindow != null)
            {
                var key = e.Text[0];
                if (completionWindow.CompletionList.ListBox.Items.Count == 0)
                    completionWindow.Close();
            }
            if (completionWindow != null || area == null) return;
            completionWindow = completionHelper.BuildList(area, () =>
            {
                var result = new WPF.TestEditor.Controls.AvalonTextEditor.CompletionWindow(area);
                return result;
            });
            if (completionWindow == null) return;
            completionWindow.Show();
            completionWindow.Closed += delegate
            {
                completionWindow = null;
            };
        }
        void TextArea_TextEntering(object sender, TextCompositionEventArgs e)
        {
            if (e.Text.Length > 0 && completionWindow != null)
            {
                var key = e.Text[0];
                if (!char.IsLetterOrDigit(key) && key!=',' && key!='=' && key!=' ' && key!=':')
                {
                    // Whenever a non-letter is typed while the completion window is open,
                    // insert the currently selected element.
                    completionWindow.CompletionList.RequestInsertion(e);
                }                
            }
            // do not set e.Handled=true - we still want to insert the character that was typed
        }

        public void InitEditor()
        {
            //initialize text editor
            var allActions = TestDataContext.ActionManager.GetAll();
            if (normalizedActions == null)
                normalizedActions = allActions.Where(ai => !ai.IsAbstract).ToList();
            if (actions == null)
                actions = allActions.Where(ai => ai.Parent == null).ToList();
            if (actionsRegEx == null)
            {
                actionsRegEx = new Regex(string.Join("|", TransforActionsToRegEx(allActions)), RegexOptions.IgnoreCase | RegexOptions.Compiled);                
            }
            constsTestsRegEx = new Regex(
                "(?:\\s*c:(" + string.Join("|", TestDataContext.ConstantsManager.GetAll().Select(c => EscapeSpecChars(c.Name.ToLowerInvariant()))) + ")\\s*)|"
                +
                "("+string.Join("|", TestDataContext.TestInfoManager.GetProjectTests().Select(t => EscapeSpecChars(t.Name.ToLowerInvariant())))+")"
                , RegexOptions.IgnoreCase | RegexOptions.Compiled);


            if (txtEditor.SyntaxHighlighting == null || !txtEditor.SyntaxHighlighting.Name.Equals("TestEditor"))
                txtEditor.SyntaxHighlighting = GetSyntaxHighlighter("TestEditor", AppProperties.Settings.Default.TestEditorHighlightingTemplate, (template) =>
                {
                    var rb = new StringBuilder();
                    return template;
                });
            ShowInfo("text editor has been reInitialized ");
        }

        private IEnumerable<string> TransforActionsToRegEx(IEnumerable<TestAction> actions)
        {
            var result =actions.Where(a => a.Parent == null)
                .Select(a =>new {id=a.Id, exp= string.Concat("^\\s*", EscapeSpecChars(a.Name), "\\s*"), subactions=actions.Where(sa=>sa.ParentId.Equals(a.Id))})
                .Select(i=>{
                    if(i.subactions.Any())
                         return string.Concat(i.exp,",\\s*(?:",string.Join("|",i.subactions.Select(sa=>EscapeSpecChars(sa.Name))),")\\s*$");
                    else
                      return i.exp+"$";
                }).ToArray();
            return result;
            //if (action.Parent != null)
            //    return "^\\s*" + EscapeSpecChars(action.Parent.Name) + "\\s*,\\s*" + EscapeSpecChars(action.Name) + "\\s*$";
            //else
            //    return "^\\s*" + EscapeSpecChars(action.Name) + "\\s*$";
        }

        private string EscapeSpecChars(string s)
        {

            StringBuilder sb = new StringBuilder(s);

            var r = "[\\^$.|?*+(){}";
            for (int i = 0; i < r.Length; i++)
                sb.Replace(r[i].ToString(), "\\" + r[i].ToString());
            s = sb.ToString();
            if (s.IndexOf(" ") > -1)
            {
                s = string.Join("\\s+", s.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries));
            }
            return s;
        }
        [DebuggerHidden]
        [DebuggerStepThrough]
        private IHighlightingDefinition GetSyntaxHighlighter(string name, string filePath, Func<string, string> processFileText=null)
        {
            var themePath = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(filePath), AppProperties.Settings.Default.CurrentTheme, System.IO.Path.GetFileName(filePath));
            if (System.IO.File.Exists(themePath))
                filePath = themePath;
            if (!System.IO.File.Exists(filePath))
            {
                TestDataContext.Log.Error("Can't find xml highlighting template by path:" + filePath);
                return null;
            }            
            var xmlText = System.IO.File.ReadAllText(filePath);
            if (processFileText!=null) xmlText = processFileText(xmlText);
            if (string.IsNullOrEmpty(xmlText))
            {
                TestDataContext.Log.Error("Empty xml highlighting template, path:" + filePath);
                return null;
            }
            IHighlightingDefinition result = null;
            using (var tr = new System.IO.StringReader(xmlText))
            using (var xr = new System.Xml.XmlTextReader(tr))
            {
                result = ICSharpCode.AvalonEdit.Highlighting.Xshd.HighlightingLoader.Load(xr, HighlightingManager.Instance);
            }
            var existing = HighlightingManager.Instance.GetDefinition(name);
            if (existing == null)
                HighlightingManager.Instance.RegisterHighlighting(name, new string[] { ".test.cvs", ".test" }, result);
            else
            {
                existing.MainRuleSet.Spans.Clear();
                foreach (var item in result.MainRuleSet.Spans)
                    existing.MainRuleSet.Spans.Add(item);
            }
            return result;
        }


        private bool refreshing;
        public void RefreshTest(TestInfo data)
        {
            ShowInfo("Refresh text editor with last data:{0}", new object[]{data.Name});
            _Data = data;

            completionHelper = new DynamicACCollection(this.TestDataContext);

            refreshing = true;
            var i = 0;
            var lines = data.Steps.Count();
            if (txtEditor.Document.LineCount < lines)
            {                
                var sb = new StringBuilder("", 2 * lines - txtEditor.Document.LineCount);
                for (i = 0; i < lines - txtEditor.Document.LineCount; i++)
                    sb.Append("\r\n");
                txtEditor.Text = txtEditor.Text + sb.ToString();
                ShowInfo("Have been added few text editor lines");
            }
            else
            {                
                lines++;
                for (int k = lines; k <= txtEditor.Document.LineCount; k++)
                    txtEditor.Document.Remove(txtEditor.Document.GetLineByNumber(k));
                ShowInfo("Have been removed test text editor lines");
            }
            i = 0;
            foreach (var item in data.Steps)
            {
                i++;
                var tstxt =item.Action==null?
                    string.Concat(" = ",(item.Locator!=null?item.Locator.Value:""),item.Data!=null?", "+item.Data.Value:"")
                    :TestDataContext.TestStepManager.Convert(item);
                
                var ln = txtEditor.Document.GetLineByNumber(i);
                var lntxt = txtEditor.Document.GetText(ln).Trim();
                if (!lntxt.Equals(tstxt, StringComparison.OrdinalIgnoreCase))
                    txtEditor.Document.Replace(ln, tstxt);
            }
            ShowInfo("Test text editor has been updated with current test data !{0}", data.Name);
            //timer.Stop();
            //onTimer = () => InitEditor();
            InitEditor();
            //timer.Start();
            refreshing = false;
        }
        private TestInfo _Data;
        public TestInfo Data
        {
            get { return _Data; }
            set { _Data = value; }
        }

        void txtEditor_TextChanged(object sender, EventArgs e)
        {
            if (refreshing) return;
            timer.Stop();
            onTimer = () =>
            {
                var steps = new List<TestStep>();
                foreach (var item in txtEditor.Document.Lines)
                {
                    var lntxt = txtEditor.Document.GetText(item).Trim();
                    var step = TestDataContext.TestStepManager.Convert(lntxt);
                    if (step != null)
                        steps.Add(step);
                }
                Data.Steps = steps.ToArray();
                ShowInfo("Test has been updated,form text editor![{0}]", Data.Name);
                if (DataChanged != null) DataChanged(this, e);
            };
            timer.Start();
        }

        private void ShowInfo(string message,params object[] arguments)
        {
            StatusChanges(this, new MessageArgs(message, arguments));
        }
        private List<TestAction> normalizedActions;
        private List<TestAction> actions;
        private Regex constsTestsRegEx;
        private Regex actionsRegEx;
    }


    

}
